圖片來源:https://instantiator.dev/post/flipper-zero-app-tutorial-02/
請注意,透過 Flipper Zero 學習的資訊技術與知識,目的在於提升個人的技術能力和資安意識。我們強烈呼籲大家,絕對不要使用所學知識從事任何違法行為。您的合法使用是我們的期望,也是您自身責任的
一部分。
當我們在開發 Flipper Zero 應用程式時,每個場景都有三個關鍵函式,這些函式主要是負責管理進入場景時的資源、處理事件以及離開場景時釋放資源,如下:
*_on_enter
:當進入場景時初始化視圖及資源。*_on_event
:處理使用者輸入和自定義事件。*_on_exit
:離開場景時,釋放佔用的資源。在主選單的場景中,我們會定義一個回傳函式 test_app_menu_callback_main_menu
。這個回傳函式會接收到使用者的選擇,這個選擇值來自於 TestAppMenuSelection
。透過這個值,我們可以確認使用者選擇了哪一個選項,並將此選擇發送到場景管理器的自定義事件處理函式。這樣做的好處是將事件傳遞給場景管理器,而不是在選單回傳函式中處理事件,能夠確保每個場景都能處理自己的邏輯,提升整體的可讀性和維護性。
當我們進入主選單場景時,會調用 test_app_scene_on_enter_main_menu
函式來設定場景。這裡的工作是初始化該場景所需要的資源,並指示視圖調度器切換到對應的視圖。步驟如下:
重設選單視圖:
menu_reset(app->menu);
這行程式碼會重置選單,確保我們開始時的選單是乾淨的,不包含任何之前添加的項目。
添加選單項目:
每個選單項目都會用來表示一個選擇,並且會指派一個來自 TestAppMenuSelection
的 ID。這個 ID 在選單回呼函式中用來確認使用者做出的選擇。例如:
menu_add_item(
app->menu,
"First popup",
NULL,
TestAppMenuSelection_One,
test_app_menu_callback_main_menu,
app);
在這裡,我們為選單添加了一個名為「First popup」的選項,並指定當使用者選擇這個項目時會調用 test_app_menu_callback_main_menu
,並且將選擇項目標識(TestAppMenuSelection_One
)傳給這個回呼函式。
切換到選單視圖:
最後一步是指示視圖調度器切換到選單視圖,讓使用者能夠看到並進行選擇:
view_dispatcher_switch_to_view(app->view_dispatcher, TestAppView_Menu);
到目前為止,我們的選單場景已經成功設定好並且可以開始與使用者互動了。透過這種方式,場景管理器可以輕鬆處理每個場景的進入、退出以及事件處理,而且具有乾淨、高擴展性的應用程式架構。
當進入選單場景時,我們會遇到不同類型的事件。這些事件主要包括自定義事件、返回事件、以及時間流逝(Tick)事件。我們通過事件處理函式來處理這些事件。
事件類型被定義為 SceneManagerEventType
,具有三個主要值:
當使用者在選單中選擇某個選項時,test_app_menu_callback_main_menu
會創建 SceneManagerEventTypeCustom
類型的事件,並通過 scene_manager_handle_custom_event
發送給場景管理器。這樣的事件會被傳遞到場景管理器的 _on_event
函式,進行處理。
這些事件包含來自 TestAppEvent
的值,例如 TestAppEvent_ShowPopupOne
或 TestAppEvent_ShowPopupTwo
。處理邏輯如下:
bool consumed = false;
switch(event.type) {
case SceneManagerEventTypeCustom:
switch(event.event) {
case TestAppEvent_ShowPopupOne:
scene_manager_next_scene(app->scene_manager, TestAppScene_FirstPopup);
consumed = true;
break;
case TestAppEvent_ShowPopupTwo:
scene_manager_next_scene(app->scene_manager, TestAppScene_SecondPopup);
consumed = true;
break;
}
break;
default:
consumed = false;
break;
}
return consumed;
這段程式碼會根據不同的事件進行場景切換,例如當使用者選擇了第一個彈出視窗,則切換到 TestAppScene_FirstPopup
場景。
當離開選單場景時,我們會調用 test_app_scene_on_exit_main_menu
,用來清理場景的資源:
TestApp* app = context;
menu_reset(app->menu);
雖然在進入場景時我們也重設了選單,但在離開時再次清理資源是一個比較好的習慣,這樣可以確保場景中的資源不會長期佔用記憶體。
與選單場景相比,彈出視窗場景的邏輯相對簡單。每個彈出視窗的主要邏輯集中在 *_on_enter
函式中,用來初始化視窗和設置其內容。例如,以下程式碼設置了一個彈出視窗:
popup_reset(app->popup);
popup_set_context(app->popup, app);
popup_set_header(app->popup, "Popup One", 64, 10, AlignCenter, AlignTop);
popup_set_icon(app->popup, 10, 10, &I_cvc_36x36);
popup_set_text(app->popup, "One! One popup. Ah ah ah...", 64, 20, AlignLeft, AlignTop);
view_dispatcher_switch_to_view(app->view_dispatcher, TestAppView_Popup);
在這裡,我們透過 popup_set_header
和 popup_set_text
設置彈出視窗的標題和內容,最後使用 view_dispatcher_switch_to_view
切換到彈出視窗視圖。
當離開彈出視窗場景時,我們會調用 popup_reset
來清理視窗的內容,確保不再佔用記憶體。
在程式碼中的幾個地方,我們可以看到以 I_
為前綴的 Icon 指標。這些指標是由 ufbt 在編譯過程中自動從 images/
資料夾中的資源創建出來的。
例如,在資料夾中有幾個圖像檔案:
cvc_36x36.png
:這是一個 Count von Count 的小圖像,編譯後變為 I_cvc_36x46
。one.png
:10x10 尺寸的圖示,代表羅馬數字「i」,編譯後變為 I_one
。two.png
:10x10 尺寸的圖示,代表羅馬數字「ii」,編譯後變為 I_two
。我們可以通過將更多的 1-bit PNG 檔案添加到該資料夾中,讓它們在編譯過程中被整合進應用程式,並在程式碼中以 Icon 資源的形式使用。
首先,取得我們這邊用 instantiator.dev 在這篇部落格示範的專案示範:
git clone https://github.com/instantiator/flipper-zero-tutorial-app.git
接著,可以開始編譯應用程式:
$ ufbt
scons: Entering directory `/Users/lewiswestbury/.ufbt/current/scripts/ufbt'
CC /Users/lewiswestbury/src/personal/test_app/test_app.c
CDB /Users/lewiswestbury/src/personal/test_app/.vscode/compile_commands.json
LINK /Users/lewiswestbury/.ufbt/build/test_app_d.elf
INSTALL /Users/lewiswestbury/src/personal/test_app/dist/debug/test_app_d.elf
APPMETA /Users/lewiswestbury/.ufbt/build/test_app.fap
FAP /Users/lewiswestbury/.ufbt/build/test_app.fap
INSTALL /Users/lewiswestbury/src/personal/test_app/dist/test_app.fap
APPCHK /Users/lewiswestbury/.ufbt/build/test_app.fap
Target: 7, API: 26.0
如果我們的 ufbt 工具與 Flipper 設備上的韌體版本一致,就可以將它直接部署到 Flipper 上:
$ ufbt launch
scons: Entering directory `/Users/lewiswestbury/.ufbt/current/scripts/ufbt'
python3 "/Users/lewiswestbury/.ufbt/current/scripts/runfap.py" -s /Users/lewiswestbury/.ufbt/build/test_app.fap -t /ext/apps/Examples/test_app.fap
APPCHK /Users/lewiswestbury/.ufbt/build/test_app.fap
Target: 7, API: 26.0
2023-05-06 23:38:36,824 [INFO] Using flip_Akurisau on /dev/cu.usbmodemflip_Akurisau1
2023-05-06 23:38:36,877 [INFO] Installing "/Users/lewiswestbury/.ufbt/build/test_app.fap" to /ext/apps/Examples/test_app.fap
2023-05-06 23:38:36,916 [INFO] Sending "/Users/lewiswestbury/.ufbt/build/test_app.fap" to "/ext/apps/Examples/test_app.fap"
100%, chunk 1 of 1
2023-05-06 23:38:37,108 [INFO] Launching app: "Applications" /ext/apps/Examples/test_app.fap
如果韌體版本與 ufbt 工具不一致,Flipper 會告訴我們。此時可以透過以下指令更新開發工具的 SDK:
ufbt update --channel=[dev|rc|release]
或者可以使用 qFlipper 來更新 Flipper 的韌體,或者使用以下指令進行 USB 刷入:
ufbt flash_usb
如果使用的是 ST-link,也可以使用以下指令:
ufbt flash
通過本次的教學,我們已經介紹了如何為 Flipper Zero 應用程式初始化與啟動簡單的使用者介面。
作者提供完整的教學程式碼可以在文章最後的 References 找到,他鼓勵大家可以自由使用該專案的程式碼進行學習與修改。也建議各位去看看他的教學,並參考其他教學資源,進一步加深對 Flipper Zero 介面開發的了解。
instantiator.dev 的教學我們已經完成了,明天我們會把健身海豚教練開發完成,並結束今年的鐵人賽挑戰!
各位期待明天最後一篇吧!